package com.semeureka.fault.mina;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.apache.mina.filter.codec.demux.MessageDecoderAdapter;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.semeureka.fault.bean.Current;
import com.semeureka.fault.bean.Group;
import com.semeureka.fault.bean.Point;
import com.semeureka.fault.bean.Rawdata;
import com.semeureka.fault.bean.Warn;
import com.semeureka.fault.service.GroupService;
import com.semeureka.fault.service.PointService;
import com.semeureka.fault.util.Message;
import com.semeureka.frame.util.HexUtils;

@Component
public class P101MessageDecoder extends MessageDecoderAdapter {
	private static final Logger logger = LoggerFactory.getLogger(P101MessageDecoder.class);
	@Autowired
	private PointService pointService;
	@Autowired
	private GroupService groupService;
	@Autowired
	private SessionCache sessionCache;

	public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
		int head = in.getUnsigned();
		if (head == 0x10) { // 1、起始标志
			if (in.remaining() < 5) { // 3、报文长度
				return NEED_DATA;
			}
			int crc = HexUtils.sumupCrc(in, in.position(), 3);
			if (crc != in.skip(3).getUnsigned()) { // 4、加和校验
				return NOT_OK;
			}
			return in.getUnsigned() == 0x16 ? OK : NOT_OK; // 5、结束标志
		} else if (head == 0x68) { // 1、起始标志
			if (in.remaining() < 2) { // 2、长度位置
				return NEED_DATA;
			}
			int len = in.getUnsigned();
			if (len != in.getUnsigned()) { // 两次长度不一致
				return NOT_OK;
			}
			if (in.remaining() < len + 3) { // 3、报文长度
				return NEED_DATA;
			}
			int crc = HexUtils.sumupCrc(in, in.skip(1).position(), len); // 跳过第2个标志位
			if (crc != in.skip(len).getUnsigned()) { // 4、加和校验
				return NOT_OK;
			}
			return in.getUnsigned() == 0x16 ? OK : NOT_OK; // 5、结束标志
		}
		return NOT_OK;
	}

	public MessageDecoderResult decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
		byte[] content = null;
		byte[] hostCode = null;
		if (in.getUnsigned(in.position()) == 0x10) {
			content = new byte[6];
			in.get(content);
			hostCode = Arrays.copyOfRange(content, 2, 4);
			ArrayUtils.reverse(hostCode);
		} else {
			content = new byte[in.getUnsigned(in.position() + 1) + 6];
			in.get(content);
			hostCode = Arrays.copyOfRange(content, 5, 7);
			ArrayUtils.reverse(hostCode);
		}
		Group group = groupService.findByHostCode(hostCode);
		Rawdata rawdata = new Rawdata(content, 0, group, new Date());
		out.write(rawdata);
		if (group != null) {
			sessionCache.put(group, session);
			decodeRawdata(session, rawdata, out);
		} else {
			logger.warn("接收到未登记主机0x{}的数据：{}", HexUtils.toHex(hostCode), HexUtils.toHex(content));
		}
		return OK;
	}

	private void decodeRawdata(IoSession session, Rawdata rawdata, ProtocolDecoderOutput out) throws Exception {
		IoBuffer in = IoBuffer.wrap(rawdata.getContent());
		Group rawdataGroup = rawdata.getGroup();
		if (in.getUnsigned(0) == 0x10) {
			int ctrl = in.getUnsigned(1);
			switch (ctrl) {
			case 0x00: // 心跳
				session.write(new Rawdata(0x49, rawdataGroup)); // 主站询问链路状态
				break;
			case 0x8B: // 终端回复链路状态良好
				session.write(new Rawdata(0x40, rawdataGroup)); // 主站请求复位链路
				break;
			case 0xC9: // 终端询问链路状态
				session.write(new Rawdata(0x0B, rawdataGroup)); // 主站回复链路状态良好
				break;
			case 0xC0: // 复位请求复位链路
				session.write(new Rawdata(0x00, rawdataGroup)); // 主站复位链路确认
				break;
			case 0x80: // 开始总召、开始对时确认
				break;
			default:
				logger.warn("无效的控制码：0x{}，无法处理数据", String.format("%02X", ctrl));
				break;
			}
		} else {
			int type = in.getInt(7);
			switch (type & 0xFF00FFFF) {
			case 0x46000400: // 初始化结束
				session.write(new Rawdata(0x10, rawdataGroup));
				session.write(new Rawdata(0x64010600, rawdataGroup)); // 总召开始
				break;
			case 0x64000A00: // 总召结束
				session.write(new Rawdata(0x67010600, rawdataGroup)); // 对时开始
				break;
			case 0x1E000300: { // 单点遥信(带时标)
				int sqByte = in.get(8);
				if ((sqByte & 0x80) == 0) { // 非连续点位
					int count = sqByte;
					byte[] pointValue = new byte[2];
					in.position(13); // 定位到第一个点位开始
					for (int i = 0; i < count; i++) {
						in.get(pointValue);
						ArrayUtils.reverse(pointValue); // 反转
						Message message = parsePoint(rawdataGroup, pointValue, null, in.get(), parseP101Date(in));
						if (message != null) {
							out.write(message);
						}
					}
				}
				break;
			}
			case 0x09000300: { // 单点遥测
				int sqByte = in.get(8);
				if ((sqByte & 0x80) == 0) { // 非连续点位
					int count = sqByte;
					byte[] pointValue = new byte[2];
					in.position(13); // 定位到第一个点位开始
					for (int i = 0; i < count; i++) {
						in.get(pointValue);
						ArrayUtils.reverse(pointValue); // 反转
						int value = in.getUnsignedShort();
						Message message = parsePoint(rawdataGroup, pointValue, value, in.get(), new Date());
						if (message != null) {
							out.write(message);
						}
					}
				}
				break;
			}
			case 0x64000700: // 总召确认
			case 0x01001400: // 总召单点遥信
			case 0x03001400: // 总召双点遥信
			case 0x09001400: // 总召单点遥测
			case 0x67000700: // 对时结束
			case 0x01000300: // 单点遥信(不带时标)
			case 0x03000300: // 双点遥信(不带时标)
			case 0x1F000300: // 双点遥信(带时标)
			case 0x2F010701: // 遥调
				break;
			default:
				logger.warn("无效的数据类型：0x{}，无法解析数据", String.format("%08X", type));
				break;
			}
		}
	}

	private Message parsePoint(Group group, byte[] pointValue, Integer value, int quality, Date createTime) {
		Point point = pointService.findByTypeValue(group.getPointType(), pointValue);
		if (point != null) {
			Group realGroup = groupService.findByGroupNumber(group, point.getNumber());
			if (group != null) {
				if (point.isCurrent()) {
					return new Current(value / 10, null, realGroup, point.getPhase(), createTime);
				} else if (point.getWarnType() != null) {
					return new Warn(point.getWarnType(), realGroup, point.getPhase(), createTime);
				} else {
					logger.warn("无法解析来自{}、点位0x{}对应的数据，点位无法转换为有效数据", group, HexUtils.toHex(pointValue));
				}
			}
		}
		logger.debug("无法解析来自{}、点位0x{}对应的数据，无效的点位或者无对应设备", group, HexUtils.toHex(pointValue));
		return null;
	}

	private Date parseP101Date(IoBuffer in) {
		Calendar cal = DateUtils.toCalendar(new Date());
		int ms = in.getUnsigned() + (in.getUnsigned() << 8);
		cal.set(Calendar.MILLISECOND, ms % 1000);
		cal.set(Calendar.SECOND, ms / 1000);
		cal.set(Calendar.MINUTE, in.getUnsigned());
		cal.set(Calendar.HOUR_OF_DAY, in.getUnsigned());
		cal.set(Calendar.DAY_OF_MONTH, in.getUnsigned());
		cal.set(Calendar.MONTH, in.getUnsigned() - 1);
		int year = cal.get(Calendar.YEAR);
		cal.set(Calendar.YEAR, in.getUnsigned() + year - (year % 100));
		return cal.getTime();
	}
 
}
